20230816-154552
Python中的装饰器
Python中一般提起装饰器,可能最先想到的就是@,这本身是一个语法糖func = wrapper(func)的简介表示。下面将简单介绍下,顺带举几个实用的例子便于自身理解。
闭包
我个人感觉闭包这个概念是变量作用域的特殊称呼,也就是在函数嵌套定义过程中不同函数作用域的范围,下面还是通过代码来演示:
def startAt(x):
def incrementBy(y):
return x + y
return incrementBy
a = startAt(1)
print(f"function: {a}") # <function incrementBy at 0x >
print(f"result: {a(1)}") # 2
# 这里其实就有一个神奇的地方了,当我们调用startAt(1)时,传入的x变量,按理来说已经结束了,该被销毁,但是在后续继续调用其的返回函数时,仍然可以运行,并且“记住了”我们前面传递的x值。
从上述的代码就可以看出,函数里面嵌套定义函数的方式,会把内嵌函数定义的环境(也即,上下文Context)保存下来,这样即使外面的函数已经执行完毕,内嵌的闭包函数依然能够运行。可以利用这一种机制来记录一些信息,下面这段代码,我感觉还是很巧妙的:
def create(pos=(0, 0)):
def go(direction, step):
# 这里由于要修改外部函数的局部变量,因此需要nonlocal声明。这里也需要注意:如果内部变量和外部函数的局部变量相同,则会优先使用内部变量。当然也可以使用nonlocal声明,来使用外部函数的局部变量
nonlocal pos
new_x = pos[0] + direction[0] * step
new_y = pos[1] + direction[1] * step
pos = (new_x, new_y)
return pos
return go
player = create()
print(f"1st step: {player([1, 0], 10)}") # (10, 0)
print(f"2nd step: {player([0, 1], 20)}") # (10, 20)
print(f"3rd step: {player([-1, 1], 5)}") # (5, 25)
装饰器
其实,我感觉闭包和装饰器之间的出发思路是不太一样的,但是其都具有嵌套函数定义的形式,因此大多想把装饰器讲得深一点的文章,都会选择从闭包入手,所以这里也就随个波。
我个人感觉装饰器,本身的目的是为了减少代码改动,把一些非内聚的功能独立出去,便于维护,由于引入了@的语法糖,当然看起来也更好看。下面讲2个装饰器的例子:
无参数版
def decorator(func):
# 此处好多装饰器文章里,都喜欢写成这种全捕捉的参数传入,我不是很理解,下面再详细说明
def wrapper(*args, **kwargs):
print('in wrapper')
return func(*args, **kwargs)
return wrapper
@decorator
def funcA(x):
print(f"we are in funcA, and x={x}")
print(funcA) # <function decorator.<locals>.wrapper at 0x7f301a284f40>
# 其实上述的语法糖可以拆开, funcA = decorator(funcA),因此经过装饰器的修饰,funcA此时已经是wrapper函数了。
通过对上述代码的运行和结果,可以得到经过装饰器修饰后,函数已经被替换为wrapper了。这里有一点:wrapper能够捕捉任意的传入参数,是不是经过替换,这样的调用funcA(1, 2)是可以的呢?结果很遗憾并不行,这里就需要用到上面闭包的知识了,虽然我们原始的funcA已经被替换,但是由于是在外部函数引入的,其作为上下文,被保存了下来,因此此时仍然只能向funcA传递一个位置参数。当然我们可以用下面的代码,绕过这种限制,但是正如我在注释中写的,我感觉这种捕捉全部参数的写法不太好,容易造成误解,不如直接把wrapper的传入参数也定义为1个,同funcA保持一直!
def decorator(func):
def wrapper(*args, **kwargs):
print('in wrapper')
# 哈哈哈哈哈,这里直接就取一个,其他参数全部丢弃
return func(args[0])
return wrapper
带参数版
这里直接上代码,取自一个实际应用案例:
def retry(times, interval):
def decorator(f):
def wrapper(*args, **kwargs):
nonlocal times
while times:
try:
#print('suceeded')
return f(*args, **kwargs)
except MPRestError as ex:
if re.search(r"NoneType", str(ex)):
print("no results return")
return None
if re.search(r"No result for record", str(ex)):
print(f"no results return for {args[1]}")
return {}
else:
times -= 1
print(f'retrying {times} times.')
time.sleep(interval)
continue
return wrap
return decorator
@retry(5, 10)
def download(url):
return r.get(url)
本质上,这里的装饰器比无参数版只深了一小步,@retry(5, 10)在程序运行过程中,应该首先执行的是retry(5, 10),这里返回的是一个函数decorator,是不是就和上面的无参数版的一致了!当然这里传入的参数times和interval依然是作为上下文被储存起来,可以在 真正功能实现的wrapper里使用。当然这个例子里面对nonlocal的使用和最开始闭包的例子有点相像,这里也不再赘述。
装饰器类装饰类时的执行顺序
其实这才是一开始实际工作中遇到的问题,其中先后执行顺序的模糊,导致写的代码不能work,因此写了这篇和Python中的元类两篇记录。也算是对自己Python只是的一点补充!